
import { eHotUpdateEvent } from "./HotUpdateEvent";
import { Game_Const } from "./ManifestInfo";

export class MainHotUpdate {
    /**
     * 更新中
     */
    _updating: boolean = false;

    /**
     * 是否可以重试
     */
    _canRetry: boolean = false;

    /**
     * 热更新存储路径
     */
    _storagePath: string = "";

    /**
     * 热更新资源管理器
     */
    _am: jsb.AssetsManager = null;

    /**
     * 更新失败数量
     */
    _failCount: number = 0;

    /**
     * 版本信息
     */
    manifest: cc.Asset = null;

    log(...arg) {
        console.log("[MainHotUpdate]", ...arg);
    }

    unit8ArrayToString(array: Uint8Array): string {
        let out: string;
        let i: number;
        let len: number;
        let c: number;
        let char2: number;
        let char3: number;

        out = "";
        len = array.length;
        i = 0;
        while (i < len) {
            c = array[i++];
            switch (c >> 4) {
                case 0:
                case 1:
                case 2:
                case 3:
                case 4:
                case 5:
                case 6:
                case 7:
                    // 0xxxxxxx
                    out += String.fromCharCode(c);
                    break;
                case 12:
                case 13:
                    // 110x xxxx   10xx xxxx
                    char2 = array[i++];
                    out += String.fromCharCode(((c & 0x1f) << 6) | (char2 & 0x3f));
                    break;
                case 14:
                    // 1110 xxxx  10xx xxxx  10xx xxxx
                    char2 = array[i++];
                    char3 = array[i++];
                    out += String.fromCharCode(((c & 0x0f) << 12) | ((char2 & 0x3f) << 6) | ((char3 & 0x3f) << 0));
                    break;
            }
        }

        return out;
    }

    /**
     * 加载Manifest文件
     * @returns
     */
    loadManifest(storagePath?: string) {
        return new Promise<cc.Asset>((res, rej) => {
            storagePath && (this._storagePath = storagePath);
            !jsb.fileUtils.isDirectoryExist(this._storagePath) && jsb.fileUtils.createDirectory(this._storagePath);

            let manifestData: { [key: string]: any } = {};
            let localManifestPath = `${this._storagePath}/project.manifest`;
            cc.resources.load("manifest/project", cc.Asset, (err, resource: cc.Asset) => {
                if (jsb.fileUtils.isFileExist(localManifestPath)) {
                    let buf = jsb.fileUtils.getDataFromFile(localManifestPath);
                    manifestData = JSON.parse(this.unit8ArrayToString(new Uint8Array(buf)));
                    console.log('manifestData11111111 = ', JSON.stringify(manifestData));
                } else {
                    // 本地不存在 manifest 文件
                    if (err) {
                        manifestData = {
                            packageUrl: "",
                            remoteManifestUrl: "",
                            remoteVersionUrl: "",
                            version: "0",
                            assets: {},
                            searchPaths: new Array(),
                        };
                    } else {
                        manifestData = JSON.parse(resource["_nativeAsset"]);
                    }
                }
                console.log('manifestData222222 = ', JSON.stringify(manifestData));
                // manifestData["packageUrl"] = this.packageUrl;
                // manifestData["remoteManifestUrl"] = `${this.packageUrl}project.manifest`;
                // manifestData["remoteVersionUrl"] = `${this.packageUrl}version.manifest`;
                jsb.fileUtils.writeStringToFile(JSON.stringify(manifestData), localManifestPath);
                console.log('manifestData = ', JSON.stringify(manifestData));
                let localAsset = new cc.Asset();
                localAsset["_nativeAsset"] = JSON.stringify(manifestData);
                localAsset["_nativeUrl"] = localManifestPath;
                res(localAsset);
            });
        });
    }

    inited: boolean = false;

    packageUrl: string = "";

    /**
     * 初始化
     */
    async init(packageUrl: string) {
        if (this.inited) {
            return;
        }
        this._storagePath = jsb.fileUtils.getWritablePath() + "game-remote-asset";
        this.packageUrl = packageUrl;
        try {
            this.manifest = await this.loadManifest();
        } catch (error) {
            this.emit(eHotUpdateEvent.mainHotUpdateFinish, false);
            throw error;
        }

        console.log('manifest 33333= ', JSON.stringify(this.manifest));
        let versionCompareHandle = (localVersionName: string, remoteVersionName: string) => {
            this.log(`热更新：版本比较，本地版本：${localVersionName}，远程版本：${remoteVersionName}`);
            let vA = localVersionName.split(".");
            let vB = remoteVersionName.split(".");
            for (let i = 0; i < vA.length; ++i) {
                let a = parseInt(vA[i]);
                let b = parseInt(vB[i] || "0");
                if (a === b) {
                    continue;
                } else {
                    return a - b;
                }
            }
            if (vB.length > vA.length) {
                return -1;
            } else {
                return 0;
            }
        };

        this._am = new jsb.AssetsManager(this.manifest.nativeUrl, this._storagePath, versionCompareHandle);

        this._am.setVerifyCallback((path: string, asset: jsb.ManifestAsset) => {
            // 当资源被压缩时，我们不需要检查它的md5，因为zip文件已经被删除了。
            let compressed = asset.compressed;
            // 检索正确的md5值。
            let expectedMD5 = asset.md5;
            // asset.path 为相对路径，path为绝对路径。
            let relativePath = asset.path;
            // 资源文件的大小，但这个值可能不存在。
            let size = asset.size;

            console.log(`compressed: ${compressed},md5: ${expectedMD5},relmd5: ${asset.md5},path: ${path},relpath: ${relativePath},size: ${size}`)

            if (compressed) {
                this.log(`热更新：验证通过 ${relativePath}`);
                return true;
            } else {
                this.log(`热更新：验证通过 ${relativePath} 期望md5 ${expectedMD5}`);
                return true;
            }
        });
        this.log(" ---- 当前 main 热更版本 ---- ", this._am.getLocalManifest().getVersion());
        this._am.setMaxConcurrentTask(2);
        this.inited = true;
    }

    destroy() {
        this._am && this._am.setEventCallback(null);
        this._am = null;
    }

    /**
     * 重新下载失败资源
     */
    retry() {
        if (!this._updating && this._canRetry) {
            this._canRetry = false;
            this._am.downloadFailedAssets();
        }
    }

    /**
     * 热更新回调
     *
     * @param event
     */
    updateCallback(event) {
        let needRestart = false;
        let failed = false;
        let percent: any;
        let msg: any;
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this.log("没有发现本地manifest文件，跳过了热更新.");
                failed = true;
                break;
            // 更新进度
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                percent = event.getPercent();
                if (isNaN(percent)) return;
                msg = event.getMessage();
                this.disPatchRateEvent(percent);
                this.log("updateCallback更新进度：" + percent + ", msg: " + msg);
                break;

            // 下载manifest文件失败，跳过热更新
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                this.log("下载manifest文件失败，跳过热更新.");
                failed = true;
                break;

            // 已是最新版本
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                this.log("已是最新版本.");
                needRestart = true;
                break;
            // 更新结束
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                this.log("更新结束." + event.getMessage());
                this.disPatchRateEvent(1);
                needRestart = true;
                break;
            // 更新错误
            case jsb.EventAssetsManager.UPDATE_FAILED:
                this.log("更新错误." + event.getMessage());
                this._updating = false;
                this._canRetry = true;
                this._failCount++;
                this.retry();
                break;
            // 更新过程中错误
            case jsb.EventAssetsManager.ERROR_UPDATING:
                this.log("更新过程中错误: " + event.getAssetId() + ", " + event.getMessage());
                break;
            // 解压错误
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                this.log("解压错误");
                break;
            default:
                break;
        }

        if (failed) {
            this._am && this._am.setEventCallback(null);
            this._updating = false;
            this.hotUpdateFinish(false);
        }

        if (needRestart) {
            this._am && this._am.setEventCallback(null);

            let searchPaths = jsb.fileUtils.getSearchPaths();
            console.log('searchPaths1 = ', searchPaths);
            let newPaths = this._am.getLocalManifest().getSearchPaths();
            newPaths.forEach((path, index) => {
                if (searchPaths.indexOf(path) < 0) {
                    searchPaths.unshift(path)
                }
            })
            // Array.prototype.unshift.apply(searchPaths, newPaths);
            console.log('searchPaths2 = ', searchPaths);
            cc.sys.localStorage.setItem("HotUpdateSearchPaths", JSON.stringify(searchPaths));
            cc.sys.localStorage.setItem("HotUpdateSucceedAppVer", Game_Const.native_version);
            jsb.fileUtils.setSearchPaths(searchPaths);



            cc.audioEngine.stopAll();
            cc.game.restart();
        }
    }

    /**
     * 发现新版本后，调用此方法进行热更新
     */
    hotUpdate() {
        if (this._am && !this._updating) {
            this._am.setEventCallback(this.updateCallback.bind(this));
            if (this._am.getState() === jsb.AssetsManager.State.UNINITED) {
                let url = this.manifest.nativeUrl;
                this._am.loadLocalManifest(url);
            }
            this._failCount = 0;
            this._am.update();
            this._updating = true;
        }
    }

    /**
     * 新版本检查回调
     *
     * @param event
     */
    checkCallback(event: jsb.EventAssetsManager) {
        let newVersionFound = null;
        switch (event.getEventCode()) {
            case jsb.EventAssetsManager.ERROR_NO_LOCAL_MANIFEST:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} ERROR_NO_LOCAL_MANIFEST 没有找到本地资源包内的 Manifest `);
                newVersionFound = false;
                break;
            case jsb.EventAssetsManager.ERROR_DOWNLOAD_MANIFEST:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} ERROR_DOWNLOAD_MANIFEST 下载远程的 Manifest 失败`);
                newVersionFound = false;
                break;
            case jsb.EventAssetsManager.ERROR_PARSE_MANIFEST:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} ERROR_PARSE_MANIFEST 解析远程的 Manifest 失败`);
                newVersionFound = false;
                break;
            case jsb.EventAssetsManager.ALREADY_UP_TO_DATE:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} ALREADY_UP_TO_DATE 已经更新到最新版本`);
                newVersionFound = false;
                break;
            case jsb.EventAssetsManager.NEW_VERSION_FOUND:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} NEW_VERSION_FOUND 发现新版本`);
                newVersionFound = true;
                break;
            case jsb.EventAssetsManager.ERROR_DECOMPRESS:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} ERROR_DECOMPRESS 有资源解压失败`);
                break;
            case jsb.EventAssetsManager.UPDATE_PROGRESSION:
                console.log('event = ', event.getTotalBytes())
                console.log('event = ', event.getPercentByFile())
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} UPDATE_PROGRESSION 资源下载中`);
                break;
            case jsb.EventAssetsManager.ASSET_UPDATED:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} ASSET_UPDATED 有资源下载成功`);
                break;
            case jsb.EventAssetsManager.ERROR_UPDATING:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} ERROR_UPDATING 有资源下载失败`);
                break;
            case jsb.EventAssetsManager.UPDATE_FAILED:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} UPDATE_FAILED 更新失败`);
                newVersionFound = false;
                break;
            case jsb.EventAssetsManager.UPDATE_FINISHED:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} UPDATE_FINISHED 更新成功`);
                newVersionFound = false;
                break;
            default:
                this.log(`热更新：新版本检查回调 Code：${event.getEventCode()} 出现未定义的代码`);
                return;
        }
        if (newVersionFound != null) {
            this._am && this._am.setEventCallback(null);
            this._updating = false;
            cc.systemEvent.emit(eHotUpdateEvent.mainCheckVersionResult, newVersionFound);
        }
    }

    /**
     * 检查更新
     * @returns
     */
    checkUpdate() {
        if (this._updating) {
            this.log("检测更新中...");
            return;
        }

        this.log(`热更新：开始新版本检查`);
        this.log("热更新：当前本地 project.manifest 信息", "getManifestFileUrl", this._am.getLocalManifest().getManifestFileUrl());
        this.log("热更新：当前本地 project.manifest 信息", "getManifestRoot", this._am.getLocalManifest().getManifestRoot());
        this.log("热更新：当前本地 project.manifest 信息", "getPackageUrl", this._am.getLocalManifest().getPackageUrl());
        this.log("热更新：当前本地 project.manifest 信息", "getSearchPaths", this._am.getLocalManifest().getSearchPaths());
        this.log("热更新：当前本地 project.manifest 信息", "getVersion", this._am.getLocalManifest().getVersion());
        this.log("热更新：当前本地 project.manifest 信息", "getVersionFileUrl", this._am.getLocalManifest().getVersionFileUrl());

        if (!this._am.getLocalManifest() || !this._am.getLocalManifest().isLoaded()) {
            this.log(`热更新：当前没有指定 Local project.manifest，将跳过热更`);
            cc.systemEvent.emit(eHotUpdateEvent.mainCheckVersionResult, false);
            return;
        }

        this._updating = true;
        this._am.setEventCallback(this.checkCallback.bind(this));
        this._am.checkUpdate();
    }

    /**
     * 热更完成
     * @param result
     */
    hotUpdateFinish(result: boolean) {
        this.log("热更完成", result);
        this.destroy();
        this.emit(eHotUpdateEvent.mainHotUpdateFinish, result);
    }

    /**
     * 热更进度
     * @param percent
     */
    disPatchRateEvent(percent) {
        percent = cc.misc.clamp01(percent);
        this.emit(eHotUpdateEvent.mainHotUpdateRate, percent);
    }

    emit(key: string, arg1?: any, arg2?: any, arg3?: any, arg4?: any, arg5?: any) {
        cc.systemEvent.emit(key, arg1, arg2, arg3, arg4, arg5);
    }
}
